package com.wei.czz.common.exception;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.tool.Result;
import com.wei.czz.common.utils.ContextUtils;
import com.wei.czz.common.utils.DataUtils;
import com.wei.czz.framework.common.entity.IpAddressEntity;
import com.wei.czz.framework.common.entity.LogEntity;
import com.wei.czz.framework.common.service.AsyncService;
import com.wei.czz.framework.common.service.LogService;
import com.wei.czz.framework.common.service.impl.IpAddressServiceImpl;
import com.wei.czz.framework.security.manager.RequestLimitManager;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-03-15 14:16:00
 * className: ExceptionAdvice 全局异常处理类
 * version: 1.0
 * description:
 */
@RestControllerAdvice
@AllArgsConstructor
public class ExceptionAdvice {

    private static final Logger log = LoggerFactory.getLogger(ExceptionAdvice.class);

    private final RequestLimitManager requestLimitManager;

    private final LogService logService;

    private final IpAddressServiceImpl ipAddressService;

    private final AsyncService asyncService;

    @ExceptionHandler(Exception.class)
    public Result<Object> serviceException(HttpServletRequest request, Exception e) {
        log.error("系统异常 uri={} type={} message={}", request.getRequestURI(), request.getMethod(), e.getMessage(), e);
        return Result.fail();
    }

    @ExceptionHandler(CzzException.class)
    public Result<Object> blogError(HttpServletRequest request, CzzException e) {
        log.error("CzzException异常 uri={} type={} message={}", request.getRequestURI(), request.getMethod(),
                e.getMessage(), e);
        if (ResultEnum.SERVICE_FAIL.getCode().equals(e.getCode())) {
            // 业务异常
            return Result.getResult(e.getCode(), ResultEnum.SERVICE_FAIL.getMsg());
        }
        return Result.getResult(e.getCode(), e.getMessage());
    }

//    @ExceptionHandler(AccessDeniedException.class)
//    public Result<Object> authError(HttpServletRequest request, AccessDeniedException e) {
//        log.error("权限校验异常 => Request Url: {}, Type: {}, message: {}", request.getRequestURI(), request.getMethod(),
//                e.getMessage(), e);
//        return Result.getResult(ResultEnum.AUTH_FAIL);
//    }

    /**
     * 对象参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error("@RequestBody要求携带参数错误 uri={} type={} params={}", request.getRequestURI(), request.getMethod(),
                e.getTarget(), e);

        // 获取错误提示
        String errorMessage = this.getErrorMessage(request, e.getBindingResult());

        return Result.clientFail(errorMessage);
    }

    /**
     * 对象参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(BindException.class)
    public Result<Object> bindException(HttpServletRequest request, BindException e) {
        log.error("@ModelAttribute要求携带参数错误 uri={} type={} params={}", request.getRequestURI(), request.getMethod(),
                e.getTarget(), e);

        // 获取错误提示
        String errorMessage = this.getErrorMessage(request, e.getBindingResult());

        return Result.clientFail(errorMessage);
    }

    /**
     * 请求url携带的参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> constraintViolationException(HttpServletRequest request, ConstraintViolationException e) {
        log.error("JSR303校验请求url携带的参数发生错误 uri={} type={} message={}", request.getRequestURI(),
                request.getMethod(), e.getMessage(), e);

        StringBuilder sb = new StringBuilder();

        for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
            sb.append(constraintViolation.getMessage()).append(',');
        }
        // 删除最后一个字符‘,’
        sb.delete(sb.length() - 1, sb.length());
        String message = sb.toString();

        this.saveRequestLog(request, message);

        return Result.clientFail(message);

    }

    /**
     * ‘@RequestParam要求携带的参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<Object> missingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException e) {
        log.error("@RequestParam要求携带的参数发生错误 uri={} type={}", request.getRequestURI(), request.getMethod(), e);
        String message = e.getParameterName() + "参数不能为空";

        this.saveRequestLog(request, message);

        return Result.clientFail(message);
    }

    /**
     * 保存请求日志
     * @param request http请求对象
     * @param message 请求回复消息
     */
    private void saveRequestLog(HttpServletRequest request, String message) {
        long now = System.currentTimeMillis();

        /*
            异步保存请求日志
         */
        asyncService.execute(() -> {
            // 获取请求源的ip地址
            Long userId = ContextUtils.get(Constant.USER_ID);
            if (Objects.isNull(userId)) {
                userId = CommonEnum.ZERO.getLongValue();
            }
            String requestIp = ContextUtils.get(Constant.REQUEST_IP);

            String requestId = MDC.get(Constant.TRACE_ID);

            IpAddressEntity ipAddress = ipAddressService.getIpAddress(requestIp);
            // 请求真实地址
            String address = ipAddress.getRequestAddress();

            LogEntity logEntity = new LogEntity();
            logEntity.setUserId(userId)
                    .setIp(requestIp)
                    .setAddress(address)
                    .setUri(request.getRequestURI())
                    .setType(request.getMethod())
                    .setClassName(StringUtils.EMPTY)
                    .setMethod(StringUtils.EMPTY)
                    .setRequestId(requestId)
                    .setCode(ResultEnum.REQUEST_FAIL.getCode())
                    .setMessage(message)
                    .setStartTime(now)
                    .setEndTime(now);
            logService.saveOrEdit(logEntity);

            if (CommonEnum.ZERO.getLongValue().equals(userId)) {
                // 根据请求ip进行请求失败风控操作
                requestLimitManager.requestFail(requestIp, ResultEnum.REQUEST_FAIL.getCode());
            } else {
                // 用户请求失败风控操作
                requestLimitManager.userRequestFail(userId, ResultEnum.REQUEST_FAIL.getCode());
            }

        });
    }

    /**
     * 获取对象参数错误消息
     * @param request       http请求对象
     * @param bindingResult 错误信息绑定对象
     * @return 错误提示消息
     */
    private String getErrorMessage(HttpServletRequest request, BindingResult bindingResult) {
        StringBuilder sb = new StringBuilder();

        // 获取所有的错误字段
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            // 字段错误描述
            String defaultMessage = fieldError.getDefaultMessage();
            sb.append(defaultMessage).append(';');
        }
        String message = sb.toString();

        /*
            保存请求错误记录
         */
        this.saveRequestLog(request, message);

        return message;

//        Object target = bindingResult.getTarget();
//        if (target == null) {
//            String message = ResultEnum.PARAM_FAIL.getMsg();
//            FieldError fieldError = bindingResult.getFieldError();
//            if (fieldError != null) {
//                message = fieldError.getDefaultMessage();
//            }
//            // 不会执行这个返回逻辑
//            log.error("目标对象为空。message={}", message);
//            return message;
//        }
//        Class<?> targetClass = target.getClass();
//
//        Map<String, String> map = new HashMap<>();
//        StringBuilder sb = new StringBuilder();
//
//        Field[] fields = targetClass.getDeclaredFields();
//        for (Field field : fields) {
//            String fieldName = field.getName();
//
//            // 更新sb
//            DataUtils.replaceStringBuilder(sb, fieldName, Constant.SPLIT);
//
//            int start = fieldName.length() + Constant.SPLIT.length();
//
//            // 获取字段上的所有注解
//            Annotation[] annotations = field.getAnnotations();
//            for (int i = 0; i < annotations.length; i++) {
//                Class<? extends Annotation> annotationType = annotations[i].annotationType();
//
//                // 更新sb
//                DataUtils.replaceStringBuilder(sb, start, annotationType.getSimpleName());
//
//                map.put(sb.toString(), String.valueOf(i));
//            }
//        }
//
//        for (FieldError fieldError : bindingResult.getFieldErrors()) {
//            // 字段名称
//            String fieldName = fieldError.getField();
//            // 注解名称
//            String code = fieldError.getCode();
//
//            int start = fieldName.length() + Constant.SPLIT.length();
//
//            // 更新sb
//            DataUtils.replaceStringBuilder(sb, fieldName, Constant.SPLIT, code);
//            String fieldCode = sb.toString();
//
//            // 更新sb
//            DataUtils.replaceStringBuilder(sb, start, "check");
//            String fieldCheck = sb.toString();
//
//            String message = map.get(fieldName);
//            if (message == null) {
//                // 还没记录过改字段的消息提示
//                map.put(fieldName, fieldError.getDefaultMessage());
//
//                String index = map.get(fieldCode);
//                if (StringUtils.isNotEmpty(index)) {
//                    // 记录
//                    map.put(fieldCheck, index);
//                }
//                continue;
//            }
//            message = null;
//
//            String newIndex = map.get(fieldCode);
//            String oldIndex = map.get(fieldCheck);
//            if (!StringUtils.isAnyEmpty(newIndex, oldIndex)) {
//                int nIndex = Integer.parseInt(newIndex);
//                int oIndex = Integer.parseInt(oldIndex);
//                if (nIndex < oIndex) {
//                    // 优先使用排在前面的注解
//                    message = fieldError.getDefaultMessage();
//                    // 记录
//                    map.put(fieldCheck, newIndex);
//                }
//            }
//            if (message != null) {
//                map.put(fieldName, message);
//            }
//        }
//        // 清空sb
//        sb.delete(0, sb.length());
//        for (Map.Entry<String, String> entry : map.entrySet()) {
//            String key = entry.getKey();
//            if (key.contains(Constant.SPLIT)) {
//                continue;
//            }
//            sb.append(entry.getValue()).append(',');
//        }
//        // 删除最后一个字符‘,’
//        sb.delete(sb.length() - 1, sb.length());
//        String message = sb.toString();
//
//        /*
//            保存请求错误记录
//         */
//        this.saveRequestLog(request, message);
//
//        return message;
    }
}
